<template>
  <!-- 多模态模型 -->
  <ChatLayout
    v-loading="loading"
    modelPageLabel="本地大语言AI模型"
    :answerTextList="answerTextList"
  >
    <template #header>
      <el-select
        v-model="activeModel"
        placeholder="请选择模型"
        style="width: 240px"
        size="large"
      >
        <el-option
          v-for="item in modelList"
          :key="item.value"
          :label="item.name"
          :value="item.model"
        />
      </el-select>
    </template>
    <div class="answerContent">
      <div
        class="answerContent__item"
        v-for="(item, index) in answerTextList"
        :key="index"
        :class="{
          isAiAnswer: item.type == 'AI',
        }"
      >
        <template v-if="item.type === 'AI'">
          <div
            class="answerContent__item__avatar"
            :title="item.modelName"
            v-loading="item.loading"
          >
            AI
          </div>
          <div class="answerContent__item__content" v-html="item.content"></div>
        </template>
        <template v-else>
          <div class="answerContent__item__content">
            {{ item.content }}
          </div>
          <div class="answerContent__item__avatar">刚哥</div>
        </template>
      </div>
    </div>
    <template #footer>
      <div class="promptBox">
        <el-input
          ref="inputRef"
          v-model="formInline.prompt"
          placeholder="请输入您的问题"
          clearable
          @keyup.enter.native="onSubmit"
          size="large"
        />
        <el-button
          size="large"
          type="primary"
          :icon="Promotion"
          @click="onSubmit"
        >
          发送
        </el-button>
      </div>
    </template>
  </ChatLayout>
</template>

<script setup>
import { ref, watch, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { Promotion } from "@element-plus/icons-vue";
import MarkdownIt from "markdown-it";
// components
import ChatLayout from "@/components/ChatLayout/index.vue";
// data
const loading = ref(false);
const modelList = ref([]);
const activeModel = ref("deepseek-r1:14b");
const formInline = ref({
  prompt: "",
});
const answerTextList = ref([]);
const inputRef = ref();
let controller = null;
// methods
async function getOllamaModels() {
  // 通过 fetch 获取模型列表
  const response = await fetch("/api/tags");
  return await response.json();
}
const onSubmit = async () => {
  if (controller && controller.signal) {
    controller.abort();
  } else if (controller && !controller.signal) {
    return ElMessage.warning("请稍等，上一个问题正在回答中");
  }
  const prompt = formInline.value.prompt;
  if (!activeModel.value) {
    return ElMessage.warning("请先选择调用的模型");
  }
  if (!prompt) {
    return ElMessage.warning("请先输入您的问题");
  }
  inputRef.value.blur();
  formInline.value.prompt = "";
  answerTextList.value.push({
    loading: false,
    content: prompt,
    type: "USER",
  });
  replayFnc(prompt);
};
const transformHTML = (inputHTML) => {
  // 使用正则表达式去除 <p> 和 </p> 标签，并替换 <think> 为 <div> 和 </think> 为 </div>
  return inputHTML.replace(
    /&lt;think&gt;([\s\S]*?)&lt;\/think&gt;/g,
    function (match, content) {
      // 将 <p> 和 </p> 标签从 think 区域中去除
      const cleanedContent = content.replace(/<p>/g, "").replace(/<\/p>/g, "");
      // 替换 <think> 和 </think> 为 <div> 和 </div>
      return `<div style="color:#ccc">${cleanedContent}</div>`;
    }
  );
};
const replayFnc = async (prompt) => {
  controller = new AbortController();

  const md = new MarkdownIt();

  // 确保 answerTextList 最少有一个元素
  answerTextList.value.push({
    loading: true,
    content: "",
    modelName: activeModel.value,
    type: "AI",
  });

  const response = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: activeModel.value,
      messages: [{ role: "user", content: prompt }],
      stream: true, // 启用流式传输
    }),
    signal: controller.signal,
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  let currentAnswer = ""; // 用来暂时存储拼接的内容

  const updateAnswerText = (
    newContent,
    isLoading = false,
    isThinking = false
  ) => {
    // 更新 answerTextList 的状态
    answerTextList.value[answerTextList.value.length - 1] = {
      ...answerTextList.value[answerTextList.value.length - 1],
      loading: isLoading,
      content: `${isThinking ? "思考过程" : ""}${newContent}`,
    };
  };

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const jsonChunk = JSON.parse(chunk);

    // 拼接返回的内容
    currentAnswer += jsonChunk.message.content;
    console.log("判断：controller.signal", controller.signal);

    const isThinking = currentAnswer.includes("<think>");
    const isThinkingEnd = currentAnswer.endsWith("</think>");

    if (isThinking && !currentAnswer.includes("</think>")) {
      // 如果包含 <think> 但没有 </think>，更新中间状态
      updateAnswerText(currentAnswer, false, isThinking);
    } else if (isThinking && isThinkingEnd) {
      // 如果完整的 <think> 标签已结束，更新最终内容
      updateAnswerText(currentAnswer, false, isThinking);
    } else {
      // 有完整的 <think> 标签 内容，且已经进入回答阶段，转换为 HTML
      let htmlString = await transformHTML(md.render(currentAnswer));
      updateAnswerText(htmlString, false, isThinking);
    }
  }
};
// onMounted
onMounted(async () => {
  loading.value = true;
  const res = await getOllamaModels();
  loading.value = false;
  modelList.value = res.models;
  if (controller) {
    controller.abort();
  }
});
</script>

<style lang="scss" scoped>
.answerContent {
  height: 100%;

  &__item {
    margin-bottom: 30px;
    display: flex;
    justify-content: right;
    padding-right: 60px;
    position: relative;

    &__avatar {
      position: absolute;
      right: 0;
      top: 0;
      width: 30px;
      height: 30px;
      font-size: 12px;
      flex-shrink: 0;
      /* 防止被压缩 */
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 10px;
      border-radius: 100%;
      background-color: #409eff;
      color: white;
    }

    &__content {
      margin: 0 10px;
      padding: 10px;
      background-color: #daf8d3;
      border-radius: 6px;

      :deep(p) {
        // 过滤空的p标签
        &:empty {
          display: none !important;
        }
      }
    }

    :deep(think) {
      color: #ccc;
      display: block;
    }
  }

  .isAiAnswer {
    justify-content: left;
    padding-left: 60px;

    .answerContent__item__avatar {
      left: 0;
    }

    .answerContent__item__content {
      background-color: #f5f6f8;
    }
  }
}

.promptBox {
  display: flex;
  gap: 10px;
}
</style>
